/*
 Copyright (c) 2013- MeherTJ G.
All rights reserved. Everybody can use these code freely.
 */

#pragma once

#include <vector>
#include <memory.h>
#include <ios>

/**

 @brief
	A memory binary stream.

 class Buffer {
 	set
 	size
 	bytes
 	data
 	jump..
 	...
 }

 class BufferWriter {
 	seekp(); tellp(); write();
 }

 class BufferReader {
 	seekg(); tellg(); read();
 }

 */
namespace Zen {
	
	class BufferWriter
	{
	public:
		BufferWriter(std::vector<uint8_t> * ref)
		{
			m_out = ref;
			m_wp = (long)m_out->size();
		}

		void seekp(long pos, std::ios::seekdir dir)
		{
			switch (dir) {
				case std::ios::beg:
					m_wp = pos;
					break;
				case std::ios::cur:
					m_wp += pos;
					break;
				case std::ios::end:
					m_wp = (long)m_out->size() + pos;
					break;
			}
			if(m_wp < 0) m_wp = 0;
			if(m_wp > m_out->size()) m_wp = (long)m_out->size();
		}

		long tellg()
		{
			return m_wp;
		}

		template<typename _Type>
		bool write(_Type const & value)
		{
			return write((void const*)&value);
		}
		bool write(void const * buf, size_t size)
		{
			auto data = (const char*)buf;
			if(m_wp + (long)size > m_out->size())
			{
				m_out->resize(m_wp + size, 0);
			}
			::memcpy(m_out->data() + m_wp, data, size);
			m_wp += (long)size;
			return true;
		}
		
	protected:
		std::vector<uint8_t> * m_out;
		long m_wp;
	};


	class BufferReader
	{
	public:
		BufferReader(std::vector<uint8_t> const * ref)
		{
			m_in = ref;
			m_rp = 0;
		}

		long tellg()
		{
			return m_rp;
		}
		void seekg(long pos, std::ios::seekdir dir)
		{
			switch (dir) {
				case std::ios::beg:
					m_rp = pos;
					break;
				case std::ios::cur:
					m_rp += pos;
					break;
				case std::ios::end:
					m_rp = (long)m_in->size() + pos;
					break;
			}
			if(m_rp < 0) m_rp = 0;
			if(m_rp > m_in->size()) m_rp = (long)m_in->size();
		}
		
		size_t tellg() const
		{
			return m_rp;
		}
		
		template<typename _Type>
		bool read(_Type & value)
		{
			return read((void*)&value, sizeof(value));
		}

		bool read(void * buf, size_t size)
		{
			if(m_rp < 0 || m_rp + size > m_in->size())
				return false;

			::memmove(buf, m_in->data() + m_rp, size);

			m_rp += size;
			return true;
		}
		int get()
		{
			if(m_rp < 0 || m_rp >= m_in->size())
				return -1;
			return (int)(*m_in)[m_rp++];
		}
	protected:
		std::vector<uint8_t> const * m_in;
	private:
		long m_rp;
	};
	
	class Buffer : public BufferReader, public BufferWriter
	{
	public:
		Buffer(size_t size = 0)
		: BufferReader(&mData)
		, BufferWriter(&mData)
		{
			mData.resize(size);
		}
		
		Buffer(std::vector<uint8_t> buffer)
		: BufferReader(&mData)
		, BufferWriter(&mData)
		{
			set(buffer);
		}
		
		void set(size_t size)
		{
			mData.resize(size);
		}
		
		void set(std::vector<uint8_t> buffer)
		{
			mData = std::move(buffer);
		}
		
		void resize(size_t size)
		{
			mData.resize(size);
		}

		void clear()
		{
			mData.clear();
		}
		size_t size() const
		{
			return mData.size();
		}
		const uint8_t * bytes() const
		{
			return mData.data();
		}
		std::vector<uint8_t> const & data() const
		{
			return mData;
		}

		void readJump(long sz)
		{
			seekg(sz, std::ios::cur);
		}
		void writeJump(long sz)
		{
			seekp(sz, std::ios::cur);
		}
	protected:
		std::vector<uint8_t> mData;
	};
}
